﻿using SoundEngine3.Debug;
using SoundEngine3.MIDI;
using SoundEngine3.MIDI.Meta;
using System;
using System.Collections;
using System.Collections.Generic;
using ZquenceStudio3Host.Misc;
using MIDI_NET = CannedBytes.Midi;

namespace Zquence_Studio_3_WindowsDesktop.SoundEngine
{
    public class MidiDeviceAdapter : MidiDeviceExtendedBase, MIDI_NET.IMidiDataReceiver
    {
        private List<MidiInPort> m_availableMidiInPorts;
        private List<MidiOutPort> m_availableMidiOutPorts;
        private MIDI_NET.MidiInPort m_midiIn;
        private List<MIDI_NET.MidiOutPort> m_midiOut;

        public override string Name
        {
            get { return "MIDI.NET"; }
        }

        public MidiDeviceAdapter()
        {
            m_availableMidiInPorts = new List<MidiInPort>(32);
            m_availableMidiOutPorts = new List<MidiOutPort>(32);

            m_midiIn = new MIDI_NET.MidiInPort();
            m_midiIn.Successor = this;

            m_midiOut = new List<CannedBytes.Midi.MidiOutPort>();

            var timer = SharpDepend.Manager.GUITimerManager.CreateTimer();
            timer.Interval = new TimeSpan(0, 0, 10);
            timer.Tick += Timer_Tick;
            timer.Start();
        }

        private void Timer_Tick()
        {
            RefreshMidiPorts();
        }

        public void ShortData(int data, long timestamp)
        {
            // Get the midi data as int type and convert to a MidiData type.
            MidiData midiData = new MidiData(data);

            // Send it to the SoundEngine to process it.
            SoundEngineContext.ProcessShortData(midiData, timestamp);
        }

        public void LongData(MIDI_NET.MidiBufferStream buffer, long timestamp)
        {
            throw new NotImplementedException();
        }

        MIDI_NET.MidiInPortCapsCollection mLastMidiInCaps;
        MIDI_NET.MidiOutPortCapsCollection mLastMidiOutCaps;

        /// <summary>
        /// Refresh the midi ports collections.
        /// </summary>
        public void RefreshMidiPorts()
        {
            MIDI_NET.MidiInPortCapsCollection midiInCaps = new MIDI_NET.MidiInPortCapsCollection();
            if (mLastMidiInCaps == null)
            {
                mLastMidiInCaps = midiInCaps;
                AddMidiInPorts(midiInCaps);
            }
            if (ListChanged_MidiInPortCaps(midiInCaps, mLastMidiInCaps))
            {
                m_availableMidiInPorts.Clear();
                AddMidiInPorts(midiInCaps);
                mLastMidiInCaps = midiInCaps;
            }

            MIDI_NET.MidiOutPortCapsCollection midiOutCaps = new MIDI_NET.MidiOutPortCapsCollection();
            if (mLastMidiOutCaps == null)
            {
                mLastMidiOutCaps = midiOutCaps;
                AddMidiOutPorts(midiOutCaps);
            }
            if (ListChanged_MidiOutPortCaps(midiOutCaps, mLastMidiOutCaps))
            {
                m_availableMidiOutPorts.Clear();
                AddMidiOutPorts(midiOutCaps);
                mLastMidiOutCaps = midiOutCaps;
            }
        }

        private void AddMidiOutPorts(MIDI_NET.MidiOutPortCapsCollection midiOutCaps)
        {
            foreach (var midiport in midiOutCaps)
            {
                int portId = midiOutCaps.IndexOf(midiport);

                MidiOutPort midiOutPort = new MidiOutPort(midiport.Name, portId, this);
                m_availableMidiOutPorts.Add(midiOutPort);
                AddMidiOutput(midiOutPort);

                var port = new MIDI_NET.MidiOutPort();
                //port.Open(portId);

                m_midiOut.Add(port);
            }
        }

        private void AddMidiInPorts(MIDI_NET.MidiInPortCapsCollection midiInCaps)
        {
            foreach (var midiport in midiInCaps)
            {
                int portId = midiInCaps.IndexOf(midiport);

                MidiInPort midiInPort = new MidiInPort(midiport.Name, portId, this);
                m_availableMidiInPorts.Add(midiInPort);
                AddMidiInput(midiInPort);
            }
        }

        private bool ListChanged_MidiInPortCaps(IEnumerable list, IList withList)
        {
            int count = 0;
            foreach (MIDI_NET.MidiInPortCaps item in list)
            {
                ++count;
                // Check if exist.
                bool contains = false;
                foreach (MIDI_NET.MidiInPortCaps item2 in withList)
                {
                    if (item2.Name.Equals(item.Name) &&
                        item2.DriverVersion.Equals(item.DriverVersion))
                    {
                        contains = true;
                    }
                }
                if (!contains)
                {
                    return true;
                }
            }

            if (count != withList.Count)
            {
                return true;
            }

            return false;
        }

        private bool ListChanged_MidiOutPortCaps(IEnumerable list, IList withList)
        {
            int count = 0;
            foreach (MIDI_NET.MidiOutPortCaps item in list)
            {
                ++count;
                // Check if exist.
                bool contains = false;
                foreach (MIDI_NET.MidiOutPortCaps item2 in withList)
                {
                    if (item2.Name.Equals(item.Name) &&
                        item2.DriverVersion.Equals(item.DriverVersion))
                    {
                        contains = true;
                    }
                }
                if (!contains)
                {
                    return true;
                }
            }

            if (count != withList.Count)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Set the current midi port for input.
        /// </summary>
        /// <param name="port">Midi port in</param>
        public override void SetMidiPortIn(MidiInPort port)
        {
            if (port == null)
            {
                if (m_midiIn != null)
                {
                    if (m_midiIn.IsOpen)
                    {
                        m_midiIn.Close();
                    }
                }
            }

            if (port != null)
            {
                if (m_midiIn.PortId != (int)port.PortId)
                {
                    if (m_midiIn.IsOpen)
                    {
                        m_midiIn.Close();
                    }

                    while (m_midiIn.IsOpen)
                    {
                        // Wait...
                        SharpDepend.Manager.ThreadManager.Sleep(10);
                    }

                    try
                    {
                        m_midiIn.Open((int)port.PortId);
                        m_midiIn.Start();
                    }
                    catch (Exception error)
                    {
                        Log.Error(this, "Midi in port '" + port.PortId + "' unable to start: " + error.Message);
                    }

                    Log.Debug(this, "Midi in port '" + port.PortId + "' started.");
                }
                else
                {
                    Log.Warning(this, "Midi in port '" + port.PortId + "' is already open. Ignore to open it again.");
                }
            }
        }
        
        public override MidiPortStatus GetMidiInPortStatus()
        {
            MidiPortStatus status = MidiPortStatus.None;

            if(m_midiIn != null)
            {
                status = (MidiPortStatus)m_midiIn.Status;
            }

            return status;
        }

        public override void Dispose()
        {
            // Release midi in and out devices.
            m_midiIn.Dispose();

            foreach (var item in m_midiOut)
            {
                item.Close();
                item.Dispose();
            }
            m_midiOut.Clear();

            base.Dispose();
        }

        protected override void OnSendMidiMessages(TimeMessage message)
        {
            m_midiOut[message.Port].ShortData(message.Message.MidiData.Data);
        }
    }
}
